package hxdb 

import (
  "database/sql"
  "strings"
)

// Db interface
type DbInterface interface {
  Exec(sqlstr string, args ...interface{}) (sql.Result, error)
  One(sqlstr string, args ...interface{}) (map[string]interface{}, error)
  Query(sqlstr string, args ...interface{}) ([]map[string]interface{}, error)
}

// Db error
type DbError struct {
  sql string
  err error
}

func NewDbError(sql string, err error) DbError {
  return DbError{
    sql: sql,
    err: err,
  }
}

func (e DbError) Error() string {
  return "[" + e.sql + "], " + e.err.Error()
}

// Db object
type Db struct {
  sqlDb *sql.DB
}

// create new db struct
func NewDb(driver string, dsn string, conn int) *Db {
  db := &Db{}
  db.conn(driver, dsn, conn)
  return db
}

// Db connect
func (this *Db) conn(driver string, dsn string, conn int) {
  var err error
  this.sqlDb, err = sql.Open(driver, dsn)
  if err != nil {
    panic(err)
  }
  this.sqlDb.SetMaxIdleConns(conn)
}

// get *sql.DB struct in db object
func (this *Db) Db() *sql.DB {
  return this.sqlDb
}

// query rows with sqlstr and params
func (this *Db) Query(sqlstr string, args ...interface{}) ([]map[string]interface{}, error) {
  // query rows
  rows, err := this.sqlDb.Query(sqlstr, args...)
  if err == nil {
    defer rows.Close()
    var result []map[string]interface{}
    result, err = rowsMapper(rows)
    if err == nil {
      return result, nil
    }
  } else if err == sql.ErrNoRows {
    return nil, err
  }
  return nil, NewDbError(sqlstr, err)
}

// query one row
func (this *Db) One(sqlstr string, args ...interface{}) (map[string]interface{}, error) {
  // if no limit keyword in sqlstr, add it
  if !strings.Contains(sqlstr, "LIMIT 1") {
    sqlstr = sqlstr + " LIMIT 1"
  }
  // query and return first one
  rs, err := this.Query(sqlstr, args...)
  if err != nil {
    return nil, err
  }
  return rs[0], nil
}

// exec sqlstr and return insertId(insert) or affectRows(update,delete)
func (this *Db) Exec(sqlstr string, args ...interface{}) (sql.Result, error) {
  rs, err := this.sqlDb.Exec(sqlstr, args...)
  if err != nil {
    return nil, NewDbError(sqlstr, err)
  }
  return rs, nil
}

// create New Transaction object from a db connection
func (this *Db) Transaction() (*Transaction, error) {
  tx, err := this.sqlDb.Begin()
  if err == nil {
    return &Transaction{
      tx: tx,
    }, nil
  }
  return nil, err
}

// close db connection
func (this *Db) Close() {
  if this.sqlDb != nil {
    this.sqlDb.Close()
  }
}

// Db operation
type DbOperate struct {
  db  DbInterface
}

func NewDbOperate(db DbInterface) *DbOperate {
  return &DbOperate{
    db: db,
  }
}

func (this *DbOperate) SetDb(db DbInterface) *DbOperate {
  this.db = db
  return this
}

func (this *DbOperate) Exec(sqlstr string, args ...interface{}) (sql.Result, error) {
  return this.db.Exec(sqlstr, args...)
}

func (this *DbOperate) Query(sqlstr string, args ...interface{}) ([]map[string]interface{}, error) {
  return this.db.Query(sqlstr, args...)
}

func (this *DbOperate) One(sqlstr string, args ...interface{}) (map[string]interface{}, error) {
  return this.db.One(sqlstr, args...)
}
